package com.example.springbootdemo2023.core.aop;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.springbootdemo2023.bus.log.dto.Log;
import com.example.springbootdemo2023.bus.log.service.ILogService;
import com.example.springbootdemo2023.core.annotations.MySysLog;
import com.example.springbootdemo2023.core.util.MyJWTUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Date;

/**
 * 模块名称： 系统日志记录AOP配置类
 * 模块类型： AOP
 * 编码人：高靖博
 * 创建时间：2023/3/23
 * 联系电话：18587388612
 */

@Aspect
@Component
public class SysLogAOPConfig {

    @Resource(name = "logServiceImpl")
    private ILogService logService;

    private long methodDoStartTime;

    // 描述一个切点 - 只要调用了@MySysLog注解的方法都要进行日志记录
    @Pointcut("@annotation(com.example.springbootdemo2023.core.annotations.MySysLog)")
    public void sysLogPointCut(){}

    // 通知- 前置通知： 记录方法被调用的时间，方法执行时间计时器开始
    @Before("sysLogPointCut()")
    public void beforeMethod(JoinPoint point){
        this.methodDoStartTime = System.currentTimeMillis();
    }

    // 通知- 返回后通知 - 方法调用成功
    @AfterReturning(value = "sysLogPointCut()", returning = "result")
    public void afterReturn(JoinPoint joinPoint, Object result){

        // 获取当前时间和开始时间比较 得到方法的执行时间
        long doMethodTime = System.currentTimeMillis() - this.methodDoStartTime;

        // 编写正常日志记录
        Log myLog = new Log();

        // 在spring容器中快速获取当前线程的请求对象
        HttpServletRequest httpServletRequest = (HttpServletRequest) RequestContextHolder.getRequestAttributes().resolveReference(RequestAttributes.REFERENCE_REQUEST);

        try{

            myLog.setInsertIP(getIPAddress(httpServletRequest)); // 获取客户端IP地址
            myLog.setAccountName(getUserNameByToken(httpServletRequest)); // 当前请求操作的用户名（账户名）
            myLog.setPath(httpServletRequest.getRequestURI()); // 请求后端地址( 如： /sys/login)
            myLog.setGetTime(new Date(this.methodDoStartTime)); // 方法调用时间
            myLog.setOverTime(new Date()); // 方法执行完成返回的时间
            myLog.setDoTime(String.valueOf(doMethodTime)+"ms"); // 方法执行时间
            
            // 获取aop拦截的方法 - class字节码对象
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod(); // aop拦截的方法字节码对象

            String methodName = method.getName();// java方法名 - controller方法名称

            myLog.setMethodParam(getParamStrByObjectArr(joinPoint.getArgs()));
            myLog.setModelName(methodName);
            myLog.setMethodName(methodName);
            // 获取当前方法头部的@MySysLog注解
            MySysLog annotation = method.getAnnotation(MySysLog.class);

            if(annotation!=null){ // 方法头部编写了该注解
                String modelName = annotation.modelName();
                String methodDesc = annotation.methodDesc();
                int methodType = annotation.methodType();
                int sysType = annotation.sysType();
                myLog.setModelName(modelName);
                myLog.setMethodDesc(methodDesc);
                myLog.setMethodType(methodType);
                myLog.setSysType(sysType);
            }

            myLog.setResType(1);

            logService.save(myLog); // 日志保存成功

        }catch (Exception e){
            e.printStackTrace();
        }


    }

    // 通知 - 异常后通知 - 错误日志记录
    @AfterThrowing(value = "sysLogPointCut()", throwing = "e")
    public void afterException(JoinPoint joinPoint, Throwable e){

        // 获取当前时间和开始时间比较 得到方法的执行时间
        long doMethodTime = System.currentTimeMillis() - this.methodDoStartTime;

        // 编写正常日志记录
        Log myLog = new Log();

        // 在spring容器中快速获取当前线程的请求对象
        HttpServletRequest httpServletRequest = (HttpServletRequest) RequestContextHolder.getRequestAttributes().resolveReference(RequestAttributes.REFERENCE_REQUEST);

        try{

            myLog.setInsertIP(getIPAddress(httpServletRequest)); // 获取客户端IP地址
            myLog.setAccountName(getUserNameByToken(httpServletRequest)); // 当前请求操作的用户名（账户名）
            myLog.setPath(httpServletRequest.getRequestURI()); // 请求后端地址( 如： /sys/login)
            myLog.setGetTime(new Date(this.methodDoStartTime)); // 方法调用时间
            myLog.setOverTime(new Date()); // 方法执行完成返回的时间
            myLog.setDoTime(String.valueOf(doMethodTime)+"ms"); // 方法执行时间

            // 获取aop拦截的方法 - class字节码对象
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod(); // aop拦截的方法字节码对象

            String methodName = method.getName();// java方法名 - controller方法名称

            myLog.setMethodParam(getParamStrByObjectArr(joinPoint.getArgs()));
            myLog.setModelName(methodName);
            myLog.setMethodName(methodName);

            // 获取当前方法头部的@MySysLog注解
            MySysLog annotation = method.getAnnotation(MySysLog.class);

            if(annotation!=null){ // 方法头部编写了该注解
                String modelName = annotation.modelName();
                String methodDesc = annotation.methodDesc();
                int methodType = annotation.methodType();
                int sysType = annotation.sysType();
                myLog.setModelName(modelName);
                myLog.setMethodDesc(methodDesc);
                myLog.setMethodType(methodType);
                myLog.setSysType(sysType);
            }

            myLog.setResType(2);
            myLog.setYcMsg(e.getMessage()+"||"+e.toString().substring(80)+"...");

            logService.save(myLog); // 日志保存成功

        }catch (Exception e2){
            e2.printStackTrace();
        }
    }

    /**-------------- 工具方法 ------------------------*/

    /**
     * 获取客户端IP地址
     * @param request
     * @return
     */
    public static String getIPAddress(HttpServletRequest request){

        String ip = request.getHeader("x-forwarded-for");

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        return ip;
    }


    /**
     *从请求中获取请求头信息，从请求头获取token，token解析出userName签名属性
     * @param request
     * @return
     */
    public static String getUserNameByToken(HttpServletRequest request){

        String token = request.getHeader("token");
        if(token == null){
            return "";
        }

        // 获取token签名内容
        try{
            DecodedJWT verify = JWT.require(Algorithm.HMAC256(MyJWTUtil.getSecretKey())).build().verify(token);

            String userName = verify.getClaim("userName").toString().replaceAll("\"","");
            return userName;
        }catch (Exception e){
            return "";
        }

    }


    public static String getParamStrByObjectArr(Object [] arr){

        String str = "";

        for(Object obj : arr){
            str += obj.toString()+",";
        }

        str = str.substring(0,str.length()-1);

        return str;

    }

}
